package com.example.myfirstgame.model;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;

import com.example.myfirstgame.R;
import com.example.myfirstgame.controller.GameObject;
import com.example.myfirstgame.helper.Coordinate;
import com.example.myfirstgame.helper.DataInstance;
import com.example.myfirstgame.helper.KalmanFilter;
import com.example.myfirstgame.helper.RasterPoint;

public class Car implements GameObject {

	// ==================
	// FIELDS
	// ==================

	// unique id
	private static int counter = 0;
	private int carId = 0xc1;
	private int parkedOnPLotID = 0;
	private int carType;

	// position
	private Rect collisionRectangle = new Rect();
	private float currentX;
	private float currentY;
	private float mCurrentRotation;
	private Coordinate[] lastCoords = new Coordinate[2];
	private boolean running = true;
	// driving characteristics

	private boolean writeToLine = false;
	private boolean isParking = false;
	private boolean isDriving = false;
	private ArrayList<Coordinate> driveLine = null;
	private static Context appContext = null;
	private Matrix mCarMatrix = null;
	private int SMOOTH_INDEX = 20;
	private KalmanFilter rotationFilter = new KalmanFilter(0.0625f, 5, 1,
			1.38f, 0.05f);

	// Bitmap
	float mNewWidth;
	float mNewHeight;
	private Bitmap mCarBitmap = null;
	private static float mInternalScaleFactor = 0.6f;
	private static Paint mPaint = new Paint();
	// Final stuff

	public static final String TYPE_DEFINITION = "car";
	public static final int TYPE_SPORTSCAR = 0x218;
	private static final int RESOURCE_SPORTSCAR = R.drawable.sportscar;
	public static final int TYPE_TRUCK = TYPE_SPORTSCAR + 1;
	public static final int TYPE_STANDARD = TYPE_TRUCK + 1;
	private static final int RESOURCE_STANDARD_CAR = R.drawable.standard_car;

	// ==================
	// CONSTRUCTOR
	// ==================

	public Car(int _carType, Context _appContext) {
		this.carType = _carType;
		this.driveLine = new ArrayList<Coordinate>();

		if (_appContext != null) {
			appContext = _appContext;
			this.mCarBitmap = setBitmap(_carType, _appContext);
		}
		carId = counter;
		counter++;
	}
	
	public Car(int _carType, Context _appContext, RasterPoint pRasterPoint) {
		this.carType = _carType;
		this.driveLine = new ArrayList<Coordinate>();

		setPosition(pRasterPoint.getCoordinate());
		if (_appContext != null) {
			appContext = _appContext;
			this.mCarBitmap = setBitmap(_carType, _appContext);
			scaleObject(DataInstance.getInstance().getGameObjectScalingFactor(), pRasterPoint.getRasterSize());
		}
		carId = counter;
		counter++;
	}

	// ====================
	// GETTER SETTER
	// ====================

	public Car() {
		// Just for Type Check
	}

	public ArrayList<Coordinate> getDriveLine() {
		return this.driveLine;
	}

	public void setDriveLine(ArrayList<Coordinate> driveLine) {
		this.driveLine = driveLine;
	}

	public boolean getParkingStatus() {
		return this.isParking;
	}

	public void setParkingStatus(boolean _parkingStatus) {
		this.isParking = _parkingStatus;
	}

	public int getCarId() {
		return carId;
	}

	public void setCarId(int carId) {
		this.carId = carId;
	}

	// ==================
	// METHODS
	// ==================

	// calculates the direction which the car is driving at. SMOOTH_INDEX gives
	// the length of the driveway which is analyzed
	private float[] calcDriveDirDelta(ArrayList<Coordinate> lineList) {
		float[] delta_X_Y = new float[2];
		if (lineList.size() > SMOOTH_INDEX) {
			Coordinate co1 = lineList.get(0);
			Coordinate co2 = lineList.get(SMOOTH_INDEX);
			delta_X_Y[0] = co1.getX() - co2.getX();
			delta_X_Y[1] = co1.getY() - co2.getY();
		} else if (lineList.size() >= 2) {
			lastCoords[0] = lineList.get(0);
			lastCoords[1] = lineList.get(lineList.size() - 1);
		}
		delta_X_Y[0] = lastCoords[0].getX() - lastCoords[1].getX();
		delta_X_Y[1] = lastCoords[0].getY() - lastCoords[1].getY();
		return delta_X_Y;
	}

	public void parkOn(ParkingLot parkingLot) {
		if (this.isParking == false) {

			running = false;
			parkingLot.setEmptySpace(false);
			this.isParking = true;
			parkingLot.setParkedCarId(this.getCarId());
			float newX = parkingLot.getPosition().getX() + mNewWidth / 2;
			float newY = parkingLot.getPosition().getY() + mNewHeight / 2;
			Coordinate newPosition = new Coordinate(newX, newY);
			this.setPosition(newPosition);
			mCurrentRotation = 0;
		}
	}

	private Matrix getDrawMatrix(float degree, float newX, float newY) {
		if (this.mCarMatrix == null) {
			mCarMatrix = new Matrix();
		}
		this.mCarMatrix.reset();
		float lCurrentScaleFactor = DataInstance.getInstance().getGameObjectScalingFactor() * mInternalScaleFactor; 
		this.mCarMatrix.postScale(lCurrentScaleFactor, lCurrentScaleFactor);
		this.mCarMatrix.postTranslate(-mNewWidth / 2, -mNewHeight / 2);

		this.mCarMatrix.postRotate(degree % 360);
		this.mCarMatrix.postTranslate(newX, newY);
		return this.mCarMatrix;
	}

	// Use Kalman Filter to get it smooth
	private float calculateRotation(float x, float y) {
		if (x == 0 && y == 0) {
			return mCurrentRotation;
		}
		double c = Math.sqrt((x * x) + (y * y));
		if (c == 0) {
			return mCurrentRotation;
		}
		double q = (x * x) / c;
		double p = c - q;
		double h = Math.sqrt(p * q);
		double alpha = Math.atan2(h, p);
		alpha = alpha * 180 / Math.PI;
		float retAlpha = mCurrentRotation;
		if (Double.isNaN(alpha)) {
			retAlpha = mCurrentRotation;
		} else if (x < 0 && y > 0) {
			retAlpha = (float) alpha;
		} else if (x < 0 && y < 0) {
			retAlpha = (float) (180 - alpha);
		} else if (x > 0 && y < 0) {
			retAlpha = (float) (alpha + 180);
		} else {
			retAlpha = (float) (180 - alpha + 180);
		}
		return changeRotationFormatTo180PlusMinus(retAlpha);
	}

	private static Bitmap setBitmap(int carType, Context appContext) {
		Bitmap bitmap = null;
		int res = 0;
		if (carType == TYPE_SPORTSCAR) {
			res = RESOURCE_SPORTSCAR;
		} else if (carType == TYPE_STANDARD) {
			res = RESOURCE_STANDARD_CAR;
		} else if (carType == TYPE_TRUCK) {
		}
		if (appContext != null){ 
			bitmap = BitmapFactory.decodeResource(appContext.getResources(), res);
		}
		return bitmap;
	}

	public void listener(int actionType, float x, float y) {
		// down
		if (actionType == 0) {
			writeToLine = checkIfTouchIsOnCar(x, y);
			if (writeToLine) {
				this.driveLine.clear();
			}
		}
		// move
		else if (actionType == 1) {
			if (writeToLine) {
				this.driveLine.add(new Coordinate(x, y));
			}
		}
		// up
		else if (actionType == 2) {
			writeToLine = false;
		}
	}

	private boolean checkIfTouchIsOnCar(float touchX, float touchY) {
		// TODO does not inculde the rotation of the bitmap
		// check x range
		if (touchX >= currentX - mNewWidth / 2
				&& touchX <= currentX + mNewWidth / 2) {
			if (touchY >= currentY - mNewHeight / 2
					&& touchY <= currentY + mNewHeight / 2) {
				return true;
			}
		}
		return false;
	}

	private boolean driveIng(float x_goal, float y_goal) {

		float speed = 7.5f;
		float delta_x = x_goal - currentX;
		float delta_y = y_goal - currentY;
		float goal_dist = (float) Math.sqrt((delta_x * delta_x)
				+ (delta_y * delta_y));
		if (goal_dist >= speed) {
			float ratio = speed / goal_dist;
			float x_move = ratio * delta_x;
			float y_move = ratio * delta_y;
			currentX += x_move;
			currentY += y_move;
			return true;
		} else {
			return false;
		}
	}

	private Rect fitRect(Coordinate pPosition, float pHeight, float pWeidth,
			float pAlpha) {
		float x1 = -pWeidth / 2;
		float x2 = pWeidth / 2;
		float x3 = pWeidth / 2;
		float x4 = -pWeidth / 2;
		float y1 = pHeight / 2;
		float y2 = pHeight / 2;
		float y3 = -pHeight / 2;
		float y4 = -pHeight / 2;

		float radians = (float) ((pAlpha * Math.PI) / 180);
		float cos_a = (float) Math.cos(radians);
		float sin_a = (float) Math.sin(radians);

		float[] x_points = new float[4];
		float[] y_points = new float[4];
		x_points[0] = x1 * cos_a + y1 * sin_a;
		y_points[0] = -x1 * sin_a + y1 * cos_a;
		x_points[1] = x2 * cos_a + y2 * sin_a;
		y_points[1] = -x2 * sin_a + y2 * cos_a;
		x_points[2] = x3 * cos_a + y3 * sin_a;
		y_points[2] = -x3 * sin_a + y3 * cos_a;
		x_points[3] = x4 * cos_a + y4 * sin_a;
		y_points[3] = -x4 * sin_a + y4 * cos_a;

		float x_min = 0;
		float x_max = 0;
		float y_min = 0;
		float y_max = 0;
		for (int i = 0; i < 4; i++) {
			if (x_min < x_points[i]) {
				x_min = x_points[i];
			}
			if (y_min < y_points[i]) {
				y_min = y_points[i];
			}
			if (x_max > x_points[i]) {
				x_max = x_points[i];
			}
			if (y_max > y_points[i]) {
				y_max = y_points[i];
			}
		}
		float deltaY = Math.abs(y_max - y_min);
		float deltaX = Math.abs(x_max - x_min);
		Rect rect = new Rect((int) (pPosition.getX() - deltaX / 2),
				(int) (pPosition.getY() - deltaY / 2),
				(int) (pPosition.getX() + deltaX / 2),
				(int) (pPosition.getY() + deltaY / 2));
		return rect;
	}

	private float changeRotationFormatTo180PlusMinus(float currentRot0Till360) {
		float formatedDegree = currentRot0Till360;
		if (currentRot0Till360 > 0 && currentRot0Till360 <= 180) {
			formatedDegree = currentRot0Till360;
		} else if (currentRot0Till360 == 360 || currentRot0Till360 == 0) {
			formatedDegree = 0;
		} else if (currentRot0Till360 > 180 && currentRot0Till360 <= 360) {
			formatedDegree = currentRot0Till360 - 360;
		}
		return formatedDegree;
	}

	public static float getRotationDelta(float pCalcRot, float pCurRot) {
		if (pCalcRot < 0 && pCurRot > 0) {
			if (Math.abs(pCalcRot) < 90 && Math.abs(pCurRot) < 90) {
				float partOne = Math.abs(Math.abs(pCalcRot));
				float partTwo = Math.abs(Math.abs(pCurRot));
				return partOne + partTwo;
			} else {
				float partOne = Math.abs(180 - Math.abs(pCalcRot));
				float partTwo = Math.abs(180 - Math.abs(pCurRot));
				return -(partOne + partTwo);
			}
		} else if (pCalcRot > 0 && pCurRot < 0) {
			if (Math.abs(pCalcRot) < 90 && Math.abs(pCurRot) < 90) {
				float partOne = Math.abs(180 - Math.abs(pCalcRot));
				float partTwo = Math.abs(180 - Math.abs(pCurRot));
				return -(partOne - partTwo);
			} else {
				float partOne = Math.abs(180 - Math.abs(pCalcRot));
				float partTwo = Math.abs(180 - Math.abs(pCurRot));
				return (partOne + partTwo);
			}
		} else {
			return pCalcRot - pCurRot;
		}
	}

	public void stop() {
		this.driveLine.clear();
	}

	public void pause() {
		this.running = false;
	}

	public void restart() {
		this.running = true;
	}

	// ==================================
	// Interface Collision Object
	// ==================================

	@Override
	public String getCollisonType() {
		// TODO Auto-generated method stub
		return TYPE_DEFINITION;
	}

	@Override
	public Rect getBoundingRectangle() {

		return collisionRectangle;
	}

	@Override
	public String getGameType() {
		// TODO Auto-generated method stub
		return TYPE_DEFINITION;
	}

	@Override
	public void draw(Canvas canvas) {
		// counter
		if (driveLine != null && driveLine.size() != 0 && this.running == true) {
			Coordinate co1 = driveLine.get(0);
			float[] delta_X_Y = calcDriveDirDelta(driveLine);
			float calcRot = calculateRotation(delta_X_Y[0], delta_X_Y[1]);
			// float deltaRot = getRotationDelta(calcRot, currentRotation);
			mCurrentRotation = calcRot;
			Log.d("CAR", "Calculated Rotation: " + calcRot);
			// Log.d("CAR", "delta Rotation: " + deltaRot);
			// currentRotation = calcRot;
			Log.d("CAR", "Current Rotation: " + mCurrentRotation);
			while (!driveIng(co1.getX(), co1.getY())) {
				if (driveLine.size() != 0) {
					driveLine.remove(0);
					if (driveLine.size() != 0) {
						co1 = driveLine.get(0);
					}
				} else {
					break;
				}
			}
			Matrix mtx = getDrawMatrix(mCurrentRotation, currentX, currentY);
			mPaint.setColor(Color.RED);
			collisionRectangle = fitRect(this.getPosition(), mNewHeight,
					mNewWidth, mCurrentRotation);
			canvas.drawRect(collisionRectangle, mPaint);
			canvas.drawBitmap(mCarBitmap, mtx, null);
		} else {
			mPaint.setColor(Color.RED);
			collisionRectangle = fitRect(this.getPosition(), mNewHeight,
					mNewWidth, mCurrentRotation);
			canvas.drawRect(collisionRectangle, mPaint);
			Matrix mtx = getDrawMatrix(mCurrentRotation, currentX, currentY);
			canvas.drawBitmap(mCarBitmap, mtx, null);
		}
	}

	public void scaleObject(float pScaleFactor, int pRasterSize) {
		float lCurrentScaleFactor = pScaleFactor * mInternalScaleFactor;
		int w = mCarBitmap.getWidth();
		int h = mCarBitmap.getHeight();
		mNewHeight = h * lCurrentScaleFactor;
		mNewHeight = (float)((((int)mNewHeight)/pRasterSize)*pRasterSize);
		mNewWidth = w * lCurrentScaleFactor;
		mNewWidth = (float)((((int)mNewWidth)/pRasterSize)*pRasterSize);
	}

	@Override
	public void setPosition(Coordinate coordinate) {
		this.currentX = coordinate.getX();
		this.currentY = coordinate.getY();
		lastCoords[0] = new Coordinate(coordinate.getX(), coordinate.getY());
		lastCoords[1] = new Coordinate(coordinate.getX(), coordinate.getY());
	}

	@Override
	public Coordinate getPosition() {
		return new Coordinate(currentX, currentY);
	}
}
